package com.korion.channel

import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.Log
import okio.IOException
import java.lang.Exception
import java.net.InetSocketAddress
import java.net.Socket
import java.util.concurrent.*


interface IChannel{
    fun setListener(listener: ChannelListener?)
    fun connect()
    fun isConnect(): Boolean
    fun close()
    fun send(bytes: ByteArray): Boolean
    fun send(text: String): Boolean
}

interface ChannelListener{
    fun onConnected(channel: IChannel, host: String, port: Int)
    fun onFailure(error: String?)
    fun onMessage(bytes: ByteArray, size: Int)
}

class Channel private constructor(connection: Connection): IChannel {


    constructor(host: String, port: Int): this(Connection(host, port)) {
        mRemoteHost = host
        mRemotePort = port
    }

    constructor(socket: Socket): this(Connection(socket)){
        val remoteSocketAddress = socket.remoteSocketAddress as InetSocketAddress
        mRemoteHost = remoteSocketAddress.hostName
        mRemotePort = remoteSocketAddress.port
    }

    private val TAG = "Channel"
    private var mRemoteHost: String? = null
    private var mRemotePort: Int = 0
    private var mListener: ChannelListener? = null
    private val mHandler = Handler(Looper.getMainLooper())
    private val mConnection: Connection = connection
    private var mThreadPool = Executors.newScheduledThreadPool(2)
    private val mPostQueue = ConcurrentLinkedQueue<Message>()
    private lateinit var mSendFuture: Future<*>
    private lateinit var mReceiveFuture: Future<*>

    private val connectTask = Runnable {
        try {
            if (mConnection.isConnected()){
                return@Runnable
            }
            mConnection.connect()
            startReadTask()
            runOnMainThread{
                mListener?.onConnected(this, mRemoteHost!!, mRemotePort)
            }
        } catch (e: IOException){
            runOnMainThread {
                mListener?.onFailure(e.message)
            }
            close()
        }
    }

    private fun startReadTask() {
        if (mReceiveFuture.isDone || mReceiveFuture.isCancelled){
            mReceiveFuture = mThreadPool.submit(readTask)
        }
    }

    private val readTask = Runnable {
        while (mConnection.isConnected()){
            try {
                val size = mConnection.readInt()
                if (size > 0) {
                    val data = ByteArray(size)
                    val result = mConnection.read(data)
                    Log.i(TAG, "read: length = $size, actual length = $result")
                    if (result > 0){
                        runOnMainThread {  mListener?.onMessage(data, result) }
                    }
                }
            } catch (e: Exception) {
                if (!mConnection.isConnected()){
                    runOnMainThread {
                        mListener?.onFailure(e.message)
                    }
                    close()
                }
            }
        }
    }

    private val writeTask = Runnable {
        try {
            while (mPostQueue.isNotEmpty()){
                val msg = mPostQueue.poll() ?: break
                when(msg.what){
                    MessageType.DATA -> {
                        val data = msg.obj as ByteArray
                        val size = data.size
                        mConnection.writeInt(size)
                        mConnection.write(data)
                    }
                }
            }
        } catch (e: Exception){
            runOnMainThread {
                mListener?.onFailure(e.message)
            }
            close()
        }
    }


    override fun connect(){
        if (!mConnection.isConnected()){
            synchronized(this){
                if (!mConnection.isConnected()){
                    mThreadPool.execute(connectTask)
                }
            }
        }
    }

    override fun close(){
        mPostQueue.clear()
        mThreadPool.shutdown()
        mConnection.close()
    }

    override fun isConnect() = mConnection.isConnected()

    override fun send(bytes: ByteArray): Boolean{
        if (!mConnection.isConnected()){
            return false
        }
        val msg = Message.obtain()
        msg.what = MessageType.DATA
        msg.obj = bytes
        mPostQueue.offer(msg)
        if (mSendFuture.isDone || mSendFuture.isCancelled){
            mSendFuture = mThreadPool.submit(writeTask)
        }
        return true
    }

    override fun send(text: String): Boolean {
        return send(text.toByteArray())
    }

    override fun setListener(listener: ChannelListener?) {
        mListener = listener
    }

    private fun runOnMainThread(block: () -> Unit) = mHandler.post(block)

}





